#include <fcntl.h>
#include <unistd.h>
#include <time.h>
#include <sys/time.h>
#include <signal.h>
#include <memory.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>

#undef PAGE_SIZE
#undef PAGE_MASK

#define APP_NAME "i2c_ctrl"
#define VERSION "3.0"
#define BUILD_DATE __DATE__

#define REG(Addr) (*(volatile unsigned int*)(Addr))
#define PAGE_SHIFT 12
#define PAGE_SIZE (1<<PAGE_SHIFT)       /* 4096byte Per Mapped Memory page */
#define PAGE_MASK (~(PAGE_SIZE-1))

#define GPIO_StartAddr    0x56000000
#define GPIO_EndAddr     0x560000DC

#define IICREG_StartAddr   0x54000000
#define IICREG_EndAddr   0x54000010

#define INTREG_StartAddr   0X4A000000
#define INTREG_EndAddr   0X4A00001C

#define CLKREG_StartAddr   0x4C000000
#define CLKREG_EndAddr   0x4C000018
#define CLKCON      0x0C

#define IICCON  0x00
#define IICSTAT    0x04
#define IICADD    0x08
#define IICDS    0x0C
#define IICLC       0x10


#define IICINTPendFLAG  (1<<4)  /* in IICCON -- 0:  1) No interrupt pending (when read).     2) Clear pending condition & Resume the operation (when write).
1:  1) Interrupt is pending (when read)         2) N/A (when write) */
#define IICACKFLAG      (1<<0)  /* in IICSTAT   0: Last-received bit is 0 (ACK was received).   1: Last-received bit is 1 (ACK was not received). */
#define IICArbiFLAG     (1<<3)  /* in IICSTAT   0: Bus arbitration successful    1: Bus arbitration failed during serial I/O */

#define SRCPND 0x00 /* Should be init with 0x00 */
#define INTMOD 0x04 /* IRQ or FIQ, set to 0x0(IRQ-normal interrupt) */
#define INTMSK 0x08 /* should set the corresponding bit to 0 to not mask the interrupt(enable) */
#define PRIORITY 0x0C /* IRQ priority control register Reset Value=0x7F */
#define INTPND 0x10 /* Indicate the interrupt request status. 0 = The interrupt has not been requested. 1 = The interrupt source has asserted the interrupt request. */
#define INTOFFSET   0x14        /* The value in the interrupt offset register shows which interrupt request of IRQ mode is in the INTPND register.This bit can be cleared automatically by clearing SRCPND and INTPND. */
#define SUBSRCPND 0x18
#define INTSUBMSK 0x1C

#define INT_IICFlag (1<<27) /* The Mask bit of the IIC in the INT_reg */

#define Failed -1
#define OK 0
#define BlockSize 0x100 /* define BlockSize of the eeprom */
//#define PageSize  8       /* define page size of the eeprom,at24c02 == 8byte, at24c04/at24c08 == 16byte, so can use 8 to fit these eeproms */

volatile unsigned int count = 0;    /* for counter */
enum I2CAction{IIC_Write=1,IIC_Read,IIC_PageWriteIntelHex,IIC_ByteWriteIntelHex};
enum I2CSCLRate{SCL100Khz=1,SCL200Khz,SCL300Khz,SCL400Khz};

const static char* SCLRATE[4] = {"100Khz","200Khz","300Khz","400Khz"};
const static char* hex_format[8] = {"hex","heX","hEx","hEX","Hex","HeX","HEx","HEX"};
const static char* bin_format[8] = {"bin","biN","bIn","bIN","Bin","BiN","BIn","BIN"};

char* RxBuffer=NULL;            /* For the read buffer*/
char* HexBinBuffer=NULL;        /* For storage the Hex Bin File's data, max 64kbytes */

static void print_usage(void)
{
    fprintf(stdout,"************Help information for %s*************\n",APP_NAME);
    fprintf(stdout,"[Usage] %s -[a:f:s:w:i:n:l:rpbh] <value>\n", APP_NAME);
    fprintf(stdout,"Version: %s, Date: %s, Author: Youri Zhang\n\n", VERSION, BUILD_DATE);
    fprintf(stdout,"\t-a : Specify the IIC slave address\n");
    fprintf(stdout,"\t-f : Specify the IIC SCL Rate. Default:100Khz\n\t\t==>100Khz(-f 1); 200Khz(-f 2); 300Khz(-f 3); 400Khz(-f 4);\n");
    fprintf(stdout,"\t-s : Specify the Sub_register index in slave\n");
    fprintf(stdout,"\t-w : Write one byte to slave, specify the write operation&value to slave's specified Sub_register\n");
    fprintf(stdout,"\t-r : Specify the read operation from slave's specified Sub_register\n");
    fprintf(stdout,"\t-i : This option is used for EEPROM, specify the input IntelHex file's path name\n");
    fprintf(stdout,"\t-n : This option is used for EEPROM, specify the EEPROM's page size(8/16 bytes per page), default:8\n");
    fprintf(stdout,"\t-p : This option is used for EEPROM, specify the write mode,'-p' is page write mode(page by page)\n");
    fprintf(stdout,"\t-b : This option is used for EEPROM, specify the write mode,'-b' is byte write mode(byte by byte)\n");
    fprintf(stdout,"\t-l : Specify the 'read'/'hex-bin' length to 'read'/'write', this option should follow 'r' or 'i'\n");
    fprintf(stdout,"\t-h : This help information\n\n");
    fprintf(stdout,"\tAction : [-w, -r, -p or -b], only one can be seclected!\n\n");
    fprintf(stdout,"\tThis program can be used for AT24C01/02/04/08/16 EERPOM.\n\tCan't be used for AT24C32/64/128/256/512/1024, because of the different WORD ADDRESS method.\n");
    fprintf(stdout,"\tMax write length is 2048 bytes one time!\n\n");
    fprintf(stdout,"Ex:\twrite 0xAA to 0xA0(slave adress)'s 0x10 Sub_register\n\t\t==> (%s -a 0xa0 -s 0x10 -w 0xaa)\n", APP_NAME);
    fprintf(stdout,"Ex:\tread 128 bytes from 0xA0(slave adress)'s 0x00 Sub_register\n\t\t==> (%s -a 0xa0 -s 0x00 -r -l 128)\n", APP_NAME);
    fprintf(stdout,"Ex:\tPage mode write '/bin/eeprom.hex' '/bin/eeprom.bin' 256 bytes data to 0xA0(slave adress)'s 0x00 Sub_register\n\t\t==> (%s -a 0xa0 -s 0x00 -i /bin/eeprom.hex -p -l 256)\n", APP_NAME);
}

/* Software timer used to generate and set the timeout value
   this value will used for waiting ACK or Penging or Stop 
   these status should occur before the timevalue */
static void set_timer(void)   
{   
    struct itimerval itv,oldtv;   
    itv.it_interval.tv_sec = 0;   
    itv.it_interval.tv_usec =6000;   /* The jiffy is about 6.2ms */
    itv.it_value.tv_sec =0;   
    itv.it_value.tv_usec = 6000; 

    setitimer(ITIMER_VIRTUAL, &itv,&oldtv);   
}   

/* Timeout timer interrupt service roution */
static void sigvtalrm_handler(int sig)   
{   
    count++;    
}   

/* disable the IIC-interrupt */
static void s3c2440_int_init(void*  REG_Addr_INT) 
{
    REG((unsigned int)REG_Addr_INT+SRCPND) |= INT_IICFlag; /* bit operand */
    REG((unsigned int)REG_Addr_INT+INTMOD) |= INT_IICFlag;
    REG((unsigned int)REG_Addr_INT+INTMSK) |= INT_IICFlag;
    REG((unsigned int)REG_Addr_INT+PRIORITY) = 0x7F;
    REG((unsigned int)REG_Addr_INT+INTPND) |= INT_IICFlag;
    REG((unsigned int)REG_Addr_INT+SUBSRCPND) |= INT_IICFlag;
    REG((unsigned int)REG_Addr_INT+INTSUBMSK) |= INT_IICFlag;
}

/*enable the IIC-interrupt*/
static void s3c2440_int_uninit(void*  REG_Addr_INT)
{
    REG((unsigned int)REG_Addr_INT+SRCPND) &= ~INT_IICFlag;
    REG((unsigned int)REG_Addr_INT+INTMOD) &= ~INT_IICFlag;
    REG((unsigned int)REG_Addr_INT+INTMSK) &= ~INT_IICFlag;
    REG((unsigned int)REG_Addr_INT+PRIORITY) = 0x7F;
    REG((unsigned int)REG_Addr_INT+INTPND) &= ~INT_IICFlag;
    REG((unsigned int)REG_Addr_INT+SUBSRCPND) &= ~INT_IICFlag;
    REG((unsigned int)REG_Addr_INT+INTSUBMSK) &= ~INT_IICFlag;
}

/* enable PCLK(50Mhz) to  IIC & GPIO */
static void s3c2440_clkcon_init(void*  REG_CLK_IIC)
{
    REG((unsigned int)REG_CLK_IIC+CLKCON) |=0x12000;
}

/* This fuction used to config the GPIO's function
   In this program it's used to config GPE14&GPE15 to IICSCL&IICSDA */
static void s3c2440_gpio_cfgpin(void* REG_Addr_GPIO,  unsigned int Addr_offset, unsigned int MaskBit, unsigned int Function)
{
    unsigned int tmp=0;

    tmp=REG((unsigned int)REG_Addr_GPIO +Addr_offset);

    if(Addr_offset>0)
    {
        /*used for GPIO Bank B-J, which use the 2 bits Mask*/
        Function &= 3;
        tmp &= ~(3<<MaskBit);
    }
    else
    {
        /*used for GPIO Bank A, which use the 1 bit Mask*/
        Function &= 1;
        tmp &= ~(1<<MaskBit);
    }

    tmp|= (Function<<MaskBit);
    REG((unsigned int)REG_Addr_GPIO +Addr_offset)=tmp;/* write back */
}

/* This fuction used to config the GPIO's Pullup function
   In this program it's used to disable the Pullup of GPE14&GPE15(these two pins have no pullup) */ 
static void s3c2440_gpio_pullup(void* REG_Addr_GPIO,  unsigned int Addr_offset, unsigned int MaskBit, unsigned int to)
{
    unsigned int tmp=0;

    tmp=REG((unsigned int)REG_Addr_GPIO +Addr_offset);
    tmp &= ~(1<<MaskBit);
    tmp |= !!to << MaskBit;
    REG((unsigned int)REG_Addr_GPIO +Addr_offset)=tmp;
}

static void i2c_master_init(void*  REG_Addr_IIC, void* REG_Addr_GPIO, int SclRate)
{
    /* config GPE14 to IICSCL, note: the maskbit is 14*2 because in GPIO bank E
       the fuction's maskbit is 2bit */
    s3c2440_gpio_cfgpin(REG_Addr_GPIO,0x40,14*2,2);
    /* config GPE15 to IICSDA, note: the maskbit is 15*2 because in GPIO bank E
       the fuction's maskbit is 2bit */
    s3c2440_gpio_cfgpin(REG_Addr_GPIO,0x40,15*2,2);
    /* Disable the Pullup since there are no pullup resistors in GPE14&GPE15(open drain)
       write 1 to disable / write 0 to enable */
    s3c2440_gpio_pullup(REG_Addr_GPIO,0x48,14,1);
    s3c2440_gpio_pullup(REG_Addr_GPIO,0x48,15,1);

    /* Set the Master SCL clock rate */
    switch(SclRate)
    {
        case SCL100Khz:
            REG((unsigned int)REG_Addr_IIC+IICCON)=0xE0;/* init with SCL 100Khz */
            break;
        case SCL200Khz:
            REG((unsigned int)REG_Addr_IIC+IICCON)=0xAF;/* init with SCL 200Khz */
            break;
        case SCL300Khz:
            REG((unsigned int)REG_Addr_IIC+IICCON)=0xA9;/* init with SCL 300Khz */
            break;
        case SCL400Khz:
            REG((unsigned int)REG_Addr_IIC+IICCON)=0xA7;/* init with SCL 400Khz */
            break;
        default:
            REG((unsigned int)REG_Addr_IIC+IICCON)=0xE0;/* default init with SCL 100Khz */
            break;
    }

    fprintf(stdout, "Initialize the SCL Rate with [%s]...\n",SCLRATE[SclRate-1]);

    REG((unsigned int)REG_Addr_IIC+IICADD)= 0x10;    /* config S3C2440's own address 0x10*/
    REG((unsigned int)REG_Addr_IIC+IICSTAT)=0x11;   /* IIC bus data output enable(Rx/Tx)*/
    REG((unsigned int)REG_Addr_IIC+IICLC)=0x06; /* filter enable & SDA line 10 clocks delay*/
}

static inline int wait_for_ack(void*  REG_Addr_IIC, unsigned int ms10)
{
    count=0;    /* clear the count, re-start the timer count */

    while(1)
    {

        /* Wait for ACK, if ACK returned before timeout, the while(1) will break
           and this fuction return 0, the program will go on */
        if(!(REG((unsigned int)REG_Addr_IIC+IICSTAT) & IICACKFLAG))
            break;


        /* count++  is from the software interrupt--Timer.
           if the 'count' reach the timeout value && the ACK haven't return,
           ACK error will occur, this fuction will return 1, and all program will quit */
        if (count >= ms10)
        {
            fprintf(stderr, "[Error]: IIC Bus ACK Error, program quit!\n");
            count=0;
            return Failed;
        }
    }
    return OK;
}

static inline int wait_for_pending(void*  REG_Addr_IIC, unsigned int ms10)
{
    count=0;    /* clear the count, re-start the timer count */

    while(1)
    {
        /* Wait for Pending, if Pending status assert before timeout, the while(1) will break and this fuction return 0, the program will go on */
        if(REG((unsigned int)REG_Addr_IIC+IICCON) & IICINTPendFLAG)
            break;



        /* count++  is from the software interrupt--Timer.
           if the 'count' reach the timeout value && the Pending status haven't been assert,
           Pending error will occur, this fuction will return 1, and all program will quit */
        if (count >= ms10)
        {
            fprintf(stderr, "[Error]: IIC Bus Pending Error, program quit!\n");
            count=0;
            return Failed;
        }
    }
    return OK;
}

static inline void wait_for_stop(unsigned int ms10)
{
    unsigned int i;
    count=0;    /* clear the count, re-start the timer count */

    while(1)
    {
        /* this delay must be used */
        for(i=0;i<10;i++);

        if (count >= ms10)
            break;
    }
}

/* Master transmit mode
   Action Note: In this mode, Master should write the register_addr to slave first 
   then Master can write the data to the specified register in slave.
   Must clear the pending bit when write data to IICDS or after generate a start/stop */
/*** like: REG((unsigned int)REG_Addr_IIC+IICCON) &= (~IICINTPendFLAG); ***/
static int i2c_mt_write_onebyte(void*  REG_Addr_IIC, unsigned int Slave_Addr, unsigned int Sub_reg, unsigned int Data) 
{
    fprintf(stdout,"Data is [0x%02X]\n",Data);
    /* Specify the slave address and generate the start condition */
    REG((unsigned int)REG_Addr_IIC+IICSTAT) |= 0xC0; /* config to Master Tx Mode(optional) */
    REG((unsigned int)REG_Addr_IIC+IICDS) = Slave_Addr; /* Specify slave addr and shift this addr to IIC data_reg */
    REG((unsigned int)REG_Addr_IIC+IICCON) &= (~IICINTPendFLAG);/* clear the pending bit(used:when write data to IICDS 
                                                                   or after generate a start/stop) */
    REG((unsigned int)REG_Addr_IIC+IICSTAT) = 0xF0; /* generate the start(Master Transmit) condition */
    if (wait_for_ack(REG_Addr_IIC,200))
    {
        fprintf(stderr,"[Error]: IIC Slave Addr not match\n");
        return Failed;
    }
    if(wait_for_pending(REG_Addr_IIC,200))   return Failed;
    REG((unsigned int)REG_Addr_IIC+IICCON) &= (~IICINTPendFLAG);

    /* Specify the register in the slave to be write */
    REG((unsigned int)REG_Addr_IIC+IICDS) = Sub_reg;        /* sub-register_Addr in slave to be write */
    REG((unsigned int)REG_Addr_IIC+IICCON) &= (~IICINTPendFLAG);
    if(wait_for_ack(REG_Addr_IIC,200))   return Failed;
    if(wait_for_pending(REG_Addr_IIC,200))   return Failed;

    /* Write the data to the specified register in the slave */
    REG((unsigned int)REG_Addr_IIC+IICDS) = Data;           /* data to write to */
    REG((unsigned int)REG_Addr_IIC+IICCON) &= (~IICINTPendFLAG);
    if(wait_for_ack(REG_Addr_IIC,200))   return Failed;
    if(wait_for_pending(REG_Addr_IIC,200))   return Failed;

    /* Generate the stop condition */
    REG((unsigned int)REG_Addr_IIC+IICSTAT) = 0xD0;
    REG((unsigned int)REG_Addr_IIC+IICCON) &= (~IICINTPendFLAG);
    wait_for_stop(2);   /* Wait for the stop condition and the EEPROM's internally-timed write cycle(10ms max) takes effect */
    return OK;
}

/* Master receive mode
   Action Note: In this mode, Master should write the register_addr to slave first 
   then Master can receive the data from the specified register in slave.
   Must clear the pending bit when write data to IICDS or after generate a start/stop */
/*** like: REG((unsigned int)REG_Addr_IIC+IICCON) &= (~IICINTPendFLAG); ***/
static int i2c_mr_read_bytes(void*  REG_Addr_IIC, char* buf, unsigned int Slave_Addr, unsigned int Start_Addr, unsigned int length) 
{
    unsigned int i=0;

    /* Specify the slave address and generate the start condition */
    REG((unsigned int)REG_Addr_IIC+IICSTAT) |= 0xC0; /* config to Master Tx Mode(optional) */
    REG((unsigned int)REG_Addr_IIC+IICDS) = Slave_Addr; /* Specify slave addr and shift this addr to IIC data_reg */
    REG((unsigned int)REG_Addr_IIC+IICCON) &= (~IICINTPendFLAG);/* clear the pending bit(used:when write data to IICDS 
                                                                   or after generate a start/stop) */
    REG((unsigned int)REG_Addr_IIC+IICSTAT) = 0xF0; /* generate the start(Master Transmit) condition */
    if(wait_for_ack(REG_Addr_IIC,200))
    {
        fprintf(stderr,"[Error]: IIC Slave Addr not match\n");
        return Failed;
    }
    if(wait_for_pending(REG_Addr_IIC,200))   return Failed;
    REG((unsigned int)REG_Addr_IIC+IICCON) &= (~IICINTPendFLAG);

    /* Specify the register in the slave to be write */
    REG((unsigned int)REG_Addr_IIC+IICDS) = Start_Addr;     /* sub-register_Addr in slave to be write */
    REG((unsigned int)REG_Addr_IIC+IICCON) &= (~IICINTPendFLAG);
    if(wait_for_ack(REG_Addr_IIC,200))   return Failed;
    if(wait_for_pending(REG_Addr_IIC,200))   return Failed;

    /* Start the Master Receive action */
    REG((unsigned int)REG_Addr_IIC+IICSTAT) |= 0x80;    /* config to Master Rx Mode(optional) */
    REG((unsigned int)REG_Addr_IIC+IICDS) = Slave_Addr; /* Specify slave addr and shift this addr to IIC data_reg */
    REG((unsigned int)REG_Addr_IIC+IICCON) &= (~IICINTPendFLAG);
    REG((unsigned int)REG_Addr_IIC+IICSTAT) = 0xB0; /* generate the start(Master Receive) condition */
    if(wait_for_ack(REG_Addr_IIC,200))   return Failed;
    if(wait_for_pending(REG_Addr_IIC,200))   return Failed;

    fprintf(stdout,"Reading [%d] bytes from slave\n",length);

    /* Receive the bytes one by one, then storage to the allocated buf */
    for(i=0;i<length;i++)
    {
        if(i == length-1)
            REG((unsigned int)REG_Addr_IIC+IICCON) &= 0x7F;/* Disable the ACK generation when reach the last byte */

        REG((unsigned int)REG_Addr_IIC+IICCON) &= (~IICINTPendFLAG);
        if(wait_for_pending(REG_Addr_IIC,200))   return Failed; /* When received one byte, there is a pending status occur */


        *(buf+i) = REG((unsigned int)REG_Addr_IIC+IICDS);  
    }

    /* Generate the stop condition */
    REG((unsigned int)REG_Addr_IIC+IICSTAT) = 0x90;
    REG((unsigned int)REG_Addr_IIC+IICCON) &= (~IICINTPendFLAG);
    for(i=0; i<200000; i++);/* Short time delay wait for the stop condition takes effect */
    return OK;
}

/* Deal with the IntelHex file, storage the valid data in the hex to the allocated buf */
static int load_intelhex_array(char* IntelHexName, char* buf, unsigned int length)
{
    FILE *hexfile;
    unsigned int i;
    unsigned int linelength=0;
    unsigned int startbyte=9;
    unsigned int combyte=0;

    unsigned char linedata[140];    /* For general/max: 139chars in a line(:40) */
    unsigned char temp[2];          /* signed / unsigned char are accepted here */

    /*open the Intel hex file */
    if((hexfile = fopen(IntelHexName, "r")) == NULL)
    {
        fprintf(stderr,"[Error]: Openning Intelhex File... \n");
        return Failed;
    }
    else
    {
        fscanf(hexfile, "%s", linedata);
        temp[0] = linedata[1];
        temp[1] = linedata[2];
        temp[2] = '\0';
        sscanf(temp, "%x", &linelength); /* Get the valid data length in a line of the hex file(:10 :20 :40) */
        startbyte = 9;                  /* Intelhex valid data begin num */
        while(length>0 && linelength>0)/* Process line by line */
        {
            for (i=0; i<linelength && i<length;i++)
            {
                temp[0] = linedata[startbyte];
                temp[1] = linedata[startbyte+1];
                temp[2] = '\0';
                sscanf(temp, "%02X", &combyte);
                *buf=combyte;
                startbyte+=2;
                buf++;
            }

            if(length >= linelength)
                length-=linelength;
            else
                length=0;

            fscanf(hexfile, "%s", linedata); /* Read next line into the linedata[] buf */
            temp[0] = linedata[1];
            temp[1] = linedata[2];
            temp[2] = '\0';
            sscanf(temp, "%x", &linelength);    /* Get the valid data length of this 'next line' of the hex file(:10 :20 :40) */
            startbyte = 9;                  /* Intelhex valid data begin num */
        }
    }

    fclose(hexfile);
    return length;
}

/* Deal with the Input bin file, storage the valid data in the bin to the allocated buf */
static int load_binfile_array(char* BinFileName, char* buf, unsigned int length)
{
    FILE *binfile;
    unsigned int i=0;
    unsigned int actual_length=0;
    unsigned char bindata[length]; /* must use unsigned type */

    /*open the bin file */
    if((binfile = fopen(BinFileName, "r")) == NULL)
    {
        fprintf(stderr,"[Error]: Openning Bin File... \n");
        return Failed;
    }
    else
    {
        while(length>actual_length)
        {
            fread(bindata+actual_length, sizeof(unsigned char), 1, binfile);   /* Read data into the bindata[] buf */
            if(feof(binfile))
                break;
            actual_length++;
        }
        while(actual_length && length)
        {
            *(buf+i)=bindata[i];
            i++ ;
            actual_length-- ;
            length-- ;
        }
    }

    fclose(binfile);
    return length;
}

/* Master transmit mode,
   Action Note: In this mode, Master should write the register_addr to slave first 
   then Master can write the data to the specified register in slave.
   Due to the EEPROM is byte/page writing(8/16 bytes), so transmit the Hex Data to the slave 8 bytes one time.
   Must clear the pending bit when write data to IICDS or after generate a start/stop */
/*** like: REG((unsigned int)REG_Addr_IIC+IICCON) &= (~IICINTPendFLAG); ***/
    static int
page_write_intelhex(void*  REG_Addr_IIC, char* buf, unsigned int Slave_Addr, unsigned int Sub_reg, unsigned int length,unsigned int EEPROMBlockIndex) //Master transmit mode
{
    unsigned int i=0,j=0;

    /* Specify the slave address and generate the start condition */
    REG((unsigned int)REG_Addr_IIC+IICSTAT) |= 0xC0; /* config to Master Tx Mode(optional) */
    REG((unsigned int)REG_Addr_IIC+IICDS) = Slave_Addr; /* Specify slave addr and shift this addr to IIC data_reg */
    REG((unsigned int)REG_Addr_IIC+IICCON) &= (~IICINTPendFLAG);/* clear the pending bit(used:when write data to IICDS 
                                                                   or after generate a start/stop) */
    REG((unsigned int)REG_Addr_IIC+IICSTAT) = 0xF0; /* generate the start(Master Transmit) condition */
    if(wait_for_ack(REG_Addr_IIC,200))
    {
        fprintf(stderr,"[Error]: IIC Slave Addr not match\n");
        return Failed;
    }
    if(wait_for_pending(REG_Addr_IIC,200))   return Failed;
    REG((unsigned int)REG_Addr_IIC+IICCON) &= (~IICINTPendFLAG);

    /* Specify the register in the slave to be write */
    REG((unsigned int)REG_Addr_IIC+IICDS) = Sub_reg;        /* sub-register_Addr in slave to be write */
    REG((unsigned int)REG_Addr_IIC+IICCON) &= (~IICINTPendFLAG);
    if(wait_for_ack(REG_Addr_IIC,200))   return Failed;
    if(wait_for_pending(REG_Addr_IIC,200))   return Failed;

    for(i=0; i<length; i++)
    {
        /* Write the data to the specified register in the slave */
        REG((unsigned int)REG_Addr_IIC+IICDS) = *(buf+i);   /* data to write to */
        REG((unsigned int)REG_Addr_IIC+IICCON) &= (~IICINTPendFLAG);
        if(wait_for_ack(REG_Addr_IIC,200))   return Failed;
        if(wait_for_pending(REG_Addr_IIC,200)) return Failed;


        fprintf(stdout,"[0x%02X]=0x%02X\t",Sub_reg+i+EEPROMBlockIndex*BlockSize,*(buf+i));
        if((i+Sub_reg+1)%8 == 0) fprintf(stdout, "\n");

        wait_for_stop(2);   /* Wait for the EEPROM's internally-timed write cycle(10ms max) */
    }

    /* Generate the stop condition */
    REG((unsigned int)REG_Addr_IIC+IICSTAT) = 0xD0;
    REG((unsigned int)REG_Addr_IIC+IICCON) &= (~IICINTPendFLAG);
    wait_for_stop(1);   /* Short time delay wait for the stop condition takes effect */
    return OK;
}

int main(int argc, char* argv[])
{
    int fd=-1, i=0, IntelHexptr=0, ret=0, opt = -1,Value=-1, SclRate=1, Length=-1, file_flag=-1,
        Action=-1,Sub_register=-1, Slave_Addr=-1, PageSize=8, EEPROMBlockIndex=0;

    void* INTBASE = NULL;
    void* CLKBASE = NULL;
    void* IICBASE = NULL;
    void* GPIOBASE = NULL;
    char* file_path = NULL;
    char* file_format = NULL;

    signal(SIGVTALRM, sigvtalrm_handler);
    set_timer();

    if(argc==2 && !strcmp(argv[1],"-h")) {
        print_usage();
        exit(1);
    }
    else if (argc > 6 && argc <15) {
        fd = open("/dev/mem", O_RDWR | O_SYNC);
        if(fd<0) {
            fprintf(stderr, "[Error]: open /dev/mem error\n");
            exit(1);
        }

        INTBASE = mmap(NULL, (INTREG_EndAddr-INTREG_StartAddr), 
                PROT_READ|PROT_WRITE, MAP_SHARED,fd, INTREG_StartAddr & PAGE_MASK);
        if(INTBASE == NULL || INTBASE == MAP_FAILED) {
            fprintf(stderr, "[Error]: S3C2440 INT_REG base mmap error\n");
            goto failed;
        }
        fprintf(stdout, "Re-Maped 0x%X to Space Addr:0x%X, Maped Size 0x%X bytes\n",
                INTREG_StartAddr,INTBASE ,(INTREG_EndAddr-INTREG_StartAddr));

        CLKBASE = mmap(NULL, (CLKREG_EndAddr-CLKREG_StartAddr), PROT_READ|PROT_WRITE, 
                MAP_SHARED,fd, CLKREG_StartAddr & PAGE_MASK);
        if(CLKBASE == NULL || CLKBASE == MAP_FAILED) {
            fprintf(stderr, "[Error]: S3C2440 CLKCON base mmap error\n");
            goto failed;
        }
        fprintf(stdout, "Re-Maped 0x%X to Space Addr:0x%X, Maped Size 0x%X bytes\n",
                CLKREG_StartAddr,CLKBASE ,(CLKREG_EndAddr-CLKREG_StartAddr));

        IICBASE = mmap(NULL, (IICREG_EndAddr-IICREG_StartAddr), PROT_READ|PROT_WRITE, MAP_SHARED,
                fd, IICREG_StartAddr & PAGE_MASK);
        if(IICBASE == NULL || IICBASE == MAP_FAILED) {
            fprintf(stderr, "[Error]: S3C2440 IIC_REG base mmap error\n");
            goto failed;
        }
        fprintf(stdout, "Re-Maped 0x%X to Space Addr:0x%X, Maped Size 0x%X bytes\n",
                IICREG_StartAddr,IICBASE ,(IICREG_EndAddr-IICREG_StartAddr));

        GPIOBASE = mmap(NULL, (GPIO_EndAddr-GPIO_StartAddr), PROT_READ|PROT_WRITE, MAP_SHARED,fd, GPIO_StartAddr & PAGE_MASK);
        if(GPIOBASE == NULL || GPIOBASE == MAP_FAILED) {
            fprintf(stderr, "[Error]: S3C2440 gpio base mmap error\n");
            goto failed;
        }
        fprintf(stdout, "Re-Maped 0x%X to Space Addr:0x%X, Maped Size 0x%X\n",
                GPIO_StartAddr,GPIOBASE,(GPIO_EndAddr-GPIO_StartAddr));


        while ((opt = getopt(argc, argv, "a:f:s:w:i:n:l:rpbh")) != -1) {
            switch (opt) {
                case 'a':   /* Slave address */
                    Slave_Addr = strtoul(optarg, NULL, 0);
                    if( Slave_Addr < 0 || Slave_Addr > 0xFF){
                        fprintf(stderr, "[Error]: Please input correct Slave_Addr!\n");
                        goto failed;
                    }
                    break;

                case 'f':   /* SCL rate */
                    SclRate = strtoul(optarg, NULL, 0);
                    if( SclRate < 1 || SclRate > 4){
                        fprintf(stderr, "[Error]: Please input correct SCL Rate!\n");
                        goto failed;
                    }
                    break;

                case 's':   /* Sub-register in slave */
                    Sub_register = strtoul(optarg, NULL, 0);
                    break;

                case 'w':   /* Write operation(one byte) */
                    if(Action != -1){
                        fprintf(stderr, "[Error]: Please input correct option, [-w, -r, -p or -b], only one can be seclected!\n");
                        goto failed;
                    }

                    Value = strtoul(optarg, NULL, 0);
                    if( Value < 0 || Value >255){
                        fprintf(stderr, "[Error]: Please input correct value to be write!\n");
                        fprintf(stderr, "[Error]: The value to be write should between 0x00 and 0xFF!\n");
                        goto failed;
                    }

                    Action=IIC_Write;
                    break;

                case 'i':   /* Specify the IntelHex file's path(dir) */
                    file_path = optarg;
                    break;

                case 'n':   /* Specify the EEPROM's page size */
                    PageSize = strtoul(optarg, NULL, 0);
                    if( PageSize%8 != 0){
                        fprintf(stderr, "[Error]: Please input correct EEPROM page size!\n");
                        goto failed;
                    }
                    break;

                case 'l':   /* Specify the 'read'/'Intelhex' length to 'read'/'write', this option should follow 'r' or 'i' */
                    Length = strtoul(optarg, NULL, 0);
                    break;

                case 'r':   /* Read operation */
                    if(Action != -1){
                        fprintf(stderr, "[Error]: Please input correct option, [-w, -r, -p or -b], only one can be seclected!\n");
                        goto failed;
                    }

                    Action=IIC_Read;
                    break;

                case 'p':   /* Page write operation */
                    if(Action != -1 )
                    {
                        fprintf(stderr, "[Error]: Please input correct option, [-w, -r, -p or -b], only one can be seclected!\n");
                        goto failed;
                    }

                    Action=IIC_PageWriteIntelHex;
                    break;

                case 'b':   /* Byte write operation */
                    if(Action != -1)
                    {
                        fprintf(stderr, "[Error]: Please input correct option, [-w, -r, -p or -b], only one can be seclected!\n");
                        goto failed;
                    }

                    Action=IIC_ByteWriteIntelHex;
                    break;

                case 'h': /* help information */
                    print_usage();
                    goto failed;

                default :
                    fprintf(stderr, "[Error]: Uncompleted/Unknown option: %c\n", optopt);
                    print_usage();
                    goto failed;
            }
        }
    }
    else {
        fprintf(stderr, "[Error]: The format of the input options is not correct!\n");
        fprintf(stderr, "[Error]: The quantity of the input options is not correct!\n");
        fprintf(stderr, "[Error]: Please use '%s -h' to get the help information!\n", APP_NAME);
        print_usage();
        exit(1);
    }

    printf("[%s %d ]\n", __FUNCTION__, __LINE__);
    s3c2440_clkcon_init(CLKBASE);
    s3c2440_int_init(INTBASE);
    i2c_master_init(IICBASE,GPIOBASE,SclRate);

    /* Allocate buffer for these two actions (Buffer for IntelHex) */
    if(Action==IIC_PageWriteIntelHex || Action==IIC_ByteWriteIntelHex) {
        if( Length < 1 || Length >2048){
            fprintf(stderr, "[Error]: Please input correct length to be write!\n");
            fprintf(stderr, "[Error]: The hex bin file length to be write should between 1 and 2048(2KB) for one time!\n");
            goto failed;
        }

        file_format = file_path+(strlen(file_path)-3);
        for(i=0;i<8;i++) {
            if(!strcmp(file_format,hex_format[i]))
                file_flag = 0;
            else if(!strcmp(file_format,bin_format[i]))
                file_flag = 1;
        }

        if(file_flag == Failed) {
            fprintf(stderr,"[Error]: File format error, hex or bin file are accepted \n");
            goto failed;
        }
        else {
            HexBinBuffer=(char*)malloc(Length);
            if(!HexBinBuffer) {
                fprintf(stderr,"[Error]: No enough memory to alloc %d bytes!\n",Length);
                goto failed;
            }
            memset(HexBinBuffer,'\0',Length); /* Initialize the buffer with '\0' */
            if(!file_flag)  /* load intelhex file data*/ {
                if((ret=load_intelhex_array(file_path,HexBinBuffer,Length))> 0)
                    fprintf(stderr,"[Warning]: The input IntelHex file only contains %d bytes valid data!\n",Length-ret);
            }
            else    /* load bin file data */ {
                if((ret=load_binfile_array(file_path,HexBinBuffer,Length))> 0)
                    fprintf(stderr,"[Warning]: The input Bin file only contains %d bytes valid data!\n",Length-ret);    
            }
            if(ret == Failed)
                goto failed;
            ret=Length-ret; /* Final length to write to slave */

            if((ret+Sub_register)>2048) {
                fprintf(stderr,"[Error]: Exceed EEPROM memory boundary,Sub_register(-s)+length(-l) should not exceed 2048(2KB)!\n");
                goto failed;
            }
        }
    }


    /* main action entry */
    switch (Action) {
        case IIC_Write:
            if(i2c_mt_write_onebyte(IICBASE, Slave_Addr, Sub_register, Value) != OK)
                goto failed;

            fprintf(stdout,"Now the register [0x%02X]=[0x%02X]\n",Sub_register,Value);
            break;

        case IIC_Read:
            if( Length < 1 || Length >2048){
                fprintf(stderr, "[Error]: Please input correct length to be read!\n");
                fprintf(stderr, "[Error]: The length to be read should between 1 and 2048(2KB) for one time!\n");
                goto failed;
            }

            RxBuffer=(char*)malloc(Length); /* buffer for reading data from eeprom */
            if(!RxBuffer) {
                fprintf(stderr,"[Error]: No enough memory to alloc %d bytes for read buffer!\n",Length);
                goto failed;
            }
            memset(RxBuffer,'\0',Length);/* initialize the buffer with '\0' */

            if(i2c_mr_read_bytes(IICBASE,RxBuffer,Slave_Addr, Sub_register, Length) != OK)
                goto failed;

            for(i=0;i<Length;i++)   /* print the reading data to the terminal */
            {
                fprintf(stdout,"[0x%02X]=0x%02X\t",i+Sub_register,*(RxBuffer+i));
                if((i+1) % 8 == 0 || ((i==Length-1)&(Length%8 !=0))) fprintf(stdout, "\n");
            }

            break;

        case IIC_PageWriteIntelHex:
            fprintf(stdout, "Writing [%d] bytes to EEPROM...\n",ret);

            count=0;    /* wait for the timer's last count complete */
            while(1)
                if(count==1)
                    break;

            while(ret)
            {
                Slave_Addr += Sub_register/BlockSize*2; /*256 bytes per block, when the input Sub_register is located in block 1(2), and slave address should be 0xA0+2(4) */
                EEPROMBlockIndex += Sub_register/BlockSize;
                Sub_register = Sub_register%BlockSize;

                if(ret >= PageSize)
                {
                    if(page_write_intelhex(IICBASE, HexBinBuffer+IntelHexptr, Slave_Addr, Sub_register, PageSize-Sub_register%PageSize, EEPROMBlockIndex) != OK)
                        goto failed;
                    IntelHexptr += PageSize-Sub_register%PageSize;
                    ret -= PageSize-Sub_register%PageSize;
                    Sub_register += PageSize-Sub_register%PageSize; 
                }
                else
                {
                    if(page_write_intelhex(IICBASE, HexBinBuffer+IntelHexptr, Slave_Addr, Sub_register, ret, EEPROMBlockIndex) != OK)
                        goto failed;
                    ret=0;
                }
            }   
            fprintf(stdout, "\n");
            break;

        case IIC_ByteWriteIntelHex:
            fprintf(stdout, "Writing [%d] bytes to EEPROM...\n",ret);

            count=0;    /* wait for the timer's last count complete */
            while(1)
                if(count==1)
                    break;

            while(ret)
            {
                Slave_Addr += Sub_register/BlockSize*2; /* 256 bytes per block, when the input Sub_register is located in block 1(2), and slave address should be 0xA0+2(4) */
                EEPROMBlockIndex += Sub_register/BlockSize;
                Sub_register = Sub_register%BlockSize;

                if(i2c_mt_write_onebyte(IICBASE, Slave_Addr, Sub_register, *(HexBinBuffer+IntelHexptr)) != OK)
                    goto failed;

                fprintf(stdout,"[0x%02X]=0x%02X\t", Sub_register+EEPROMBlockIndex*BlockSize, *(HexBinBuffer+IntelHexptr));
                if((i+1) % 8 == 0) fprintf(stdout, "\n");

                Sub_register ++;
                IntelHexptr ++;
                ret--;
                i++;
            }   
            fprintf(stdout, "\n");
            break;

        default:break;
    }

    s3c2440_int_uninit(INTBASE);
    if(RxBuffer)
        free(RxBuffer);
    if(HexBinBuffer)
        free(HexBinBuffer);

    munmap(INTBASE,(INTREG_EndAddr-INTREG_StartAddr)); 
    munmap(CLKBASE,(CLKREG_EndAddr-CLKREG_StartAddr)); 
    munmap(IICBASE,(IICREG_EndAddr-IICREG_StartAddr)); 
    munmap(GPIOBASE,(GPIO_EndAddr-GPIO_StartAddr)); 
    close(fd);
    exit(0);

failed:
    Slave_Addr=-1;
    Sub_register=-1;
    Value=-1; 
    Length=-1;
    Action=-1;
    s3c2440_int_uninit(INTBASE);
    if(RxBuffer)
        free(RxBuffer);
    if(HexBinBuffer)
        free(HexBinBuffer);
    munmap(INTBASE,(INTREG_EndAddr-INTREG_StartAddr)); 
    munmap(CLKBASE,(CLKREG_EndAddr-CLKREG_StartAddr)); 
    munmap(IICBASE,(IICREG_EndAddr-IICREG_StartAddr)); 
    munmap(GPIOBASE,(GPIO_EndAddr-GPIO_StartAddr)); 
    close(fd);
    INTBASE = NULL;
    CLKBASE = NULL;
    IICBASE = NULL;
    GPIOBASE = NULL;
    exit(1);
}

// http://www.jishuhome.com/os/linux/2011/05/17/1953.html
